﻿using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using Oxage.Common;

namespace Oxage.Windows.Controls
{
	public partial class Console : UserControl, IConsole
	{
		#region Private members
		private bool scroll = false;
		private BackgroundWorker worker;
		#endregion

		#region Constructor
		public Console()
		{
			InitializeComponent();

			ViewModel.Prompt = "> ";
			ViewModel.EnableEcho = true;

			this.Loaded += (sender, e) =>
			{
				input.Focus();
			};

			this.Clear();

			output.TextChanged += (sender, e) =>
			{
				//Does not always work
				scroll = true;
			};
			output.LayoutUpdated += (sender, e) =>
			{
				//Always works but cannot scroll up manually
				if (scroll)
				{
					scroll = false;
					SetInputScrollFocus();
				}
			};

			//Async worker is in experimental stage
			this.IsAsync = false;
			CreateWorker();

#if !SILVERLIGHT
			scroller.GotKeyboardFocus += output_GotKeyboardFocus;
			output.GotKeyboardFocus += output_GotKeyboardFocus;
			prompt.GotKeyboardFocus += output_GotKeyboardFocus;
#endif
		}
		#endregion

		#region Public properties
		public ConsoleViewModel ViewModel
		{
			get
			{
				return this.DataContext as ConsoleViewModel;
			}
		}

		/// <summary>
		/// Gets or sets the console input line content.
		/// </summary>
		public string Input
		{
			get
			{
				return input.Text;
			}
			set
			{
				input.Text = value;
			}
		}

		/// <summary>
		/// Gets or sets the console output content.
		/// </summary>
		public string Output
		{
			get
			{
				return output.Text;
			}
			set
			{
				output.Text = value ?? "";
				ViewModel.HasOutput = !string.IsNullOrEmpty(value);
			}
		}

		/// <summary>
		/// Gets or sets the prompt string.
		/// </summary>
		public string Prompt
		{
			get
			{
				return ViewModel.Prompt;
			}
			set
			{
				ViewModel.Prompt = value;
			}
		}

		/// <summary>
		/// Gets or sets a value indicating whether input command should automatically appear in output.
		/// </summary>
		public bool EnableEcho
		{
			get
			{
				return ViewModel.EnableEcho;
			}
			set
			{
				ViewModel.EnableEcho = value;
			}
		}

		public bool IsAsync
		{
			get;
			set;
		}
		#endregion

		#region Public methods
		public void Beep()
		{
		}

		public void Clear()
		{
			this.Output = "";
			ViewModel.HasOutput = !string.IsNullOrEmpty(this.Output);
		}

		public void Write(string content)
		{
			content = ViewModel.Split(content, 80);
			output.AppendText(content);
			ViewModel.HasOutput = !string.IsNullOrEmpty(this.Output);
		}

		public void WriteLine()
		{
			output.AppendText(Environment.NewLine);
			ViewModel.HasOutput = !string.IsNullOrEmpty(this.Output);
		}

		public void WriteLine(string content)
		{
			content = ViewModel.Split(content, 80);
			output.AppendText(Environment.NewLine + content);
			ViewModel.HasOutput = !string.IsNullOrEmpty(this.Output);
		}
		#endregion

		#region Protected methods
		protected void SetInputScrollFocus()
		{
			//Do not scroll if modifier key is pressed
			if (Keyboard.Modifiers != ModifierKeys.None)
			{
				return;
			}

			//Scroll to bottom
			scroller.ScrollToVerticalOffset(scroller.ScrollableHeight + scroller.ViewportHeight);

			//Always focus the input box
			var element = FocusManager.GetFocusedElement(this);
			if (element != input)
			{
				input.Focus();
			}
		}

		protected void HandleInput()
		{
			string line = this.Input;

			bool isComment = (!string.IsNullOrEmpty(line) && line.TrimStart().StartsWith("#"));
			if (isComment)
			{
				return;
			}

			bool isInlineEcho = (!string.IsNullOrEmpty(line) && line[0] == '@');
			if (isInlineEcho)
			{
				line = line.Substring(1);
			}

			if (ViewModel.EnableEcho && !isInlineEcho)
			{
				if (!string.IsNullOrEmpty(output.Text))
				{
					output.AppendText(Environment.NewLine);
				}
				output.AppendText(this.Prompt + line);
				ViewModel.HasOutput = true;
			}

			//TODO: Redirect output >
			//TODO: Pipeline output |
			//TODO: Return codes
			//TODO: ReadLine command during command executing
			//TODO: Run in another thread for long commands

			this.Input = "";

			if (this.IsAsync)
			{
				worker.RunWorkerAsync(line);
				input.Visibility = System.Windows.Visibility.Collapsed;
			}
			else
			{
				OnCommandExecuted(line);
			}
		}

		protected void CreateWorker()
		{
			worker = new BackgroundWorker();
			worker.WorkerReportsProgress = true;
			worker.WorkerSupportsCancellation = true;
			worker.DoWork += (sender, e) =>
			{
				Dispatcher.Invoke((CrossThreadDelegate)delegate
				{
					string line = e.Argument as string;
					OnCommandExecuted(line);
				});
			};
			worker.RunWorkerCompleted += (sender, e) =>
			{
				input.Visibility = System.Windows.Visibility.Visible;
			};
		}

		protected void DisposeWorker()
		{
			worker.DoWork -= (sender, e) => { };
			worker.RunWorkerCompleted -= (sender, e) => { };
			worker = null;
		}
		#endregion

		#region Event handlers
		private void input_KeyDown(object sender, KeyEventArgs e)
		{
			switch (e.Key)
			{
				case Key.Enter:
					HandleInput();
					e.Handled = true;
					break;

				case Key.C:
					if ((Keyboard.Modifiers & ModifierKeys.Control) > 0)
					{
						if (worker.IsBusy && worker.WorkerSupportsCancellation)
						{
							if (!worker.CancellationPending)
							{
								worker.CancelAsync();
							}
							else
							{
								DisposeWorker();
								CreateWorker();
							}
						}
					}
					break;
			}
		}

		private void output_GotFocus(object sender, RoutedEventArgs e)
		{
			input.Focus();
		}

#if !SILVERLIGHT
		private void output_GotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
		{
			input.Focus();
		}
#endif
		#endregion

		#region Bubble events
		public event EventHandler<CommandLineEventArgs> CommandExecuted;
		protected virtual void OnCommandExecuted(string line)
		{
			if (CommandExecuted != null)
			{
				CommandExecuted(this, new CommandLineEventArgs(line));
			}
		}
		#endregion		
	}

#if SILVERLIGHT
	public static class TextBoxExtensions
	{
		public static void AppendText(this TextBox textbox, string message)
		{
			textbox.Text += message;
		}
	}
#endif
}
